<template>
  <el-config-provider :locale="zhCn">
    <div class="page">
      <div class="split">
        <div class="left">
          <div class="left-toolbar">
            <el-input v-model="activityFilters.name" placeholder="搜索活动名称" clearable @input="reloadActivities" />
          </div>
          <div ref="leftListEl" class="left-list" @scroll="onLeftScroll">
            <el-table :data="activityItems" height="100%" @row-click="selectActivity">
              <el-table-column prop="name" label="活动名称" />
            </el-table>
          </div>
        </div>
        <div class="right">
          <div class="header">
            <div class="title">{{ activity?.name || '请选择活动' }}</div>
            <div class="actions">
              <el-button @click="clearSelection">清空选择</el-button>
            </div>
          </div>
          <div class="toolbar">
            <el-form :inline="true" :model="archiveFilters" class="filters">
              <el-form-item label="分类">
                <el-select v-model="archiveFilters.role" placeholder="全部" clearable style="min-width:200px" @change="reload">
                  <el-option v-for="r in roleOptions" :key="r.value" :label="r.label" :value="r.value" />
                </el-select>
              </el-form-item>
              <el-form-item label="关键词">
                <el-input v-model="archiveFilters.q" placeholder="文件名关键字" clearable @input="applyKeywordFilter" />
              </el-form-item>
              <el-form-item>
                <el-button type="primary" @click="reload">查询</el-button>
                <el-button @click="resetArchiveFilters">重置</el-button>
                <el-button type="success" @click="openUpload">上传档案</el-button>
              </el-form-item>
            </el-form>
          </div>
          <div class="content">
            <el-table :data="displayItems" style="width:100%">
              <el-table-column label="文件名" min-width="200">
                <template #default="{ row }">
                  <a :href="row.file.url" target="_blank">{{ row.file.original_name }}</a>
                </template>
              </el-table-column>
              <el-table-column label="分类" width="160">
                <template #default="{ row }">{{ roleName(row.role) }}</template>
              </el-table-column>
              <el-table-column label="大小" width="120">
                <template #default="{ row }">{{ formatSize(row.file.size_bytes) }}</template>
              </el-table-column>
              <el-table-column prop="file.created_at" label="上传时间" width="200" />
              <el-table-column fixed="right" label="操作" width="220">
                <template #default="{ row }">
                  <el-button @click="openEdit(row)">编辑</el-button>
                  <el-popconfirm title="确定删除该档案绑定？" @confirm="unbind(row)">
                    <template #reference>
                      <el-button type="danger">删除</el-button>
                    </template>
                  </el-popconfirm>
                </template>
              </el-table-column>
            </el-table>
            <div class="pagination">
              <el-pagination
                background
                layout="prev, pager, next, sizes, jumper, total"
                :total="total"
                :page-size="pageSize"
                :current-page="page"
                :page-sizes="[10,20,50,100]"
                @size-change="onSizeChange"
                @current-change="onPageChange"
              />
            </div>
          </div>
        </div>
      </div>

      <el-dialog v-model="showEdit" title="编辑档案" width="480px" destroy-on-close>
        <el-form :model="editForm" label-width="120px">
          <el-form-item label="文件名"><span>{{ currentRow?.file?.original_name || '-' }}</span></el-form-item>
          <el-form-item label="分类">
            <el-select v-model="editForm.role" style="min-width:240px">
              <el-option v-for="r in roleOptions" :key="r.value" :label="r.label" :value="r.value" />
            </el-select>
          </el-form-item>
        </el-form>
        <template #footer>
          <el-button @click="showEdit=false">取消</el-button>
          <el-button type="primary" :loading="saving" @click="submitEdit">保存</el-button>
        </template>
      </el-dialog>

      <el-dialog v-model="showUpload" title="上传档案" width="560px" destroy-on-close>
        <el-form :model="uploadForm" label-width="120px">
          <el-form-item label="活动名称"><span>{{ activity?.name || '-' }}</span></el-form-item>
          <el-form-item label="档案分类">
            <el-select v-model="uploadForm.role" style="min-width:240px">
              <el-option v-for="r in roleOptions" :key="r.value" :label="r.label" :value="r.value" />
            </el-select>
          </el-form-item>
          <el-form-item label="选择文件">
            <el-upload
              ref="uploadRef"
              :action="uploadAction"
              :auto-upload="false"
              :limit="1"
              :on-success="onUploadSuccess"
              :on-error="onUploadError"
              :before-upload="beforeUpload"
              :on-change="onFileChange"
              :headers="uploadHeaders"
              :http-request="doDavUpload"
            >
              <el-button type="primary">选择文件</el-button>
            </el-upload>
          </el-form-item>
          <el-form-item label="文件名"><span>{{ uploadForm.name || '-' }}</span></el-form-item>
          <el-form-item label="类型"><span>{{ uploadForm.mime || '-' }}</span></el-form-item>
          <el-form-item label="大小"><span>{{ formatSize(uploadForm.size || 0) }}</span></el-form-item>
        </el-form>
        <template #footer>
          <el-button @click="showUpload=false">取消</el-button>
          <el-button type="primary" :loading="saving" @click="submitUpload">上传并保存</el-button>
        </template>
      </el-dialog>

      <el-dialog v-model="showAlert" title="提示" width="380px" destroy-on-close>
        <div style="padding:8px 0">{{ alertText }}</div>
        <template #footer>
          <el-button type="primary" @click="showAlert=false">知道了</el-button>
        </template>
      </el-dialog>
    </div>
  </el-config-provider>
</template>

<script setup lang="ts">
import { ref, reactive, onMounted, computed } from 'vue'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import { useRoute, useRouter } from 'vue-router'
import { api } from '../../api/client'
import { FILE_UPLOAD_BASE, FILE_ACCESS_BASE, getUploadHeaders } from '../../config/fileservice'

type Activity = { id: string; name: string }
type DictItem = { id: string; code: string; name: string }
type FileMediaInfo = { width?: number|null; height?: number|null; duration_ms?: number|null }
type FileAssetRead = {
  id: string
  storage_key: string
  url: string
  original_name: string
  ext?: string|null
  mime_type: string
  size_bytes: number
  checksum_sha256: string
  etag?: string|null
  bucket?: string|null
  storage_provider: string
  visibility: string
  owner_id?: string|null
  uploader_id?: string|null
  expires_at?: string|null
  is_deleted?: boolean
  created_at?: string|null
  updated_at?: string|null
  media_info?: FileMediaInfo|null
}
type ArchiveItem = { file: FileAssetRead; role: string }

const route = useRoute()
const router = useRouter()
const activityId = ref<string>('')
const activity = ref<Activity|null>(null)
const page = ref(1)
const pageSize = ref(20)
const total = ref(0)
const items = ref<ArchiveItem[]>([])
const displayItems = ref<ArchiveItem[]>([])
const roles = ref<DictItem[]>([])
const roleOptions = ref<{label:string;value:string}[]>([])
const saving = ref(false)

const activityItems = ref<Activity[]>([])
const activityPage = ref(1)
const activityPageSize = ref(20)
const activityTotal = ref(0)
const activityFilters = reactive({ name: '' })
const activityLoading = ref(false)
const leftListEl = ref<HTMLDivElement | null>(null)

const archiveFilters = reactive({ role: '', q: '' })
const showEdit = ref(false)
const currentRow = ref<ArchiveItem | null>(null)
const editForm = reactive({ role: '' })

const showUpload = ref(false)
const uploadForm = reactive({ role: '', name: '', mime: '', size: 0 })
const uploadRef = ref()
const uploadAction = ref('')
const uploadHeaders = ref<Record<string, string>>({})
let pendingFile: File | null = null
const showAlert = ref(false)
const alertText = ref('')

function back() {
  router.push({ path: '/admin/activities' })
}

function clearSelection() {
  activityId.value = ''
  activity.value = null
  items.value = []
  displayItems.value = []
}

function roleName(code: string): string {
  const r = roles.value.find(x => x.code === code)
  return r ? r.name : code
}

function isMedia(code: string): boolean {
  return code === 'image' || code === 'video'
}

function formatSize(n: number): string {
  if (!n && n !== 0) return '-'
  const units = ['B','KB','MB','GB']
  let i = 0
  let x = n
  while (x >= 1024 && i < units.length - 1) { x = x / 1024; i++ }
  return `${x.toFixed(1)} ${units[i]}`
}

async function loadActivity() {
  const { data } = await api.get(`/activities/${activityId.value}`)
  activity.value = data
}

async function loadRoles() {
  const { data } = await api.get('/dictionaries/items', { params: { type_code: 'archive_category', page_size: 200 } })
  const arr = Array.isArray(data) ? data : []
  roles.value = arr
  const opts = arr.length ? arr.map((d: DictItem) => ({ label: d.name, value: d.code })) : [
    { label: '申报材料', value: 'application' },
    { label: '审批材料', value: 'approval' },
    { label: '总结材料', value: 'summary' },
    { label: '评估意见', value: 'evaluation' },
    { label: '图片资料', value: 'image' },
    { label: '视频资料', value: 'video' },
  ]
  roleOptions.value = opts
}

async function load() {
  if (!activityId.value) { items.value = []; displayItems.value = []; total.value = 0; return }
  const params: any = { page: page.value, page_size: pageSize.value }
  if (archiveFilters.role) params.role = archiveFilters.role
  try {
    const { data } = await api.get(`/file-assets/activities/${activityId.value}/files`, { params })
    const arr = Array.isArray(data.items) ? data.items : []
    items.value = arr
    total.value = typeof data.total === 'number' ? data.total : arr.length
  } catch (e: any) {
    try {
      const altParams: any = { page: page.value, page_size: pageSize.value, activity_id: activityId.value }
      if (archiveFilters.role) altParams.role = archiveFilters.role
      const { data } = await api.get('/file-assets', { params: altParams })
      const arr = Array.isArray(data.items) ? data.items : []
      items.value = arr.map((f: any) => ({ file: f, role: '' }))
      total.value = typeof data.total === 'number' ? data.total : arr.length
    } catch {
      items.value = []
      total.value = 0
    }
  }
  applyKeywordFilter()
}

function reload() {
  page.value = 1
  load()
}

function onPageChange(p: number) {
  page.value = p
  load()
}

function onSizeChange(s: number) {
  pageSize.value = s
  page.value = 1
  load()
}

function applyKeywordFilter() {
  const q = archiveFilters.q.trim().toLowerCase()
  if (!q) { displayItems.value = items.value.slice(); return }
  displayItems.value = items.value.filter(it => (it.file.original_name || '').toLowerCase().includes(q))
}

function resetArchiveFilters() {
  archiveFilters.role = ''
  archiveFilters.q = ''
  reload()
}

function openEdit(row: ArchiveItem) {
  currentRow.value = row
  editForm.role = row.role || ''
  showEdit.value = true
}

async function submitEdit() {
  if (!currentRow.value) return
  saving.value = true
  try {
    await api.put(`/file-assets/activities/${activityId.value}/files/${currentRow.value.file.id}`, { file_id: currentRow.value.file.id, role: editForm.role })
    showEdit.value = false
    await load()
  } catch (e: any) {
    alertText.value = '服务暂不支持编辑档案分类或绑定不存在'
    showAlert.value = true
  } finally {
    saving.value = false
  }
}

async function unbind(row: ArchiveItem) {
  try {
    await api.delete(`/file-assets/activities/${activityId.value}/files/${row.file.id}`)
    reload()
  } catch (e: any) {
    alertText.value = '服务暂不支持删除档案绑定或绑定不存在'
    showAlert.value = true
  }
}

function preview(row: ArchiveItem) {
  window.open(row.file.url, '_blank')
}

function openUpload() {
  if (!activityId.value) { alertText.value = '请先在左侧选择一个活动'; showAlert.value = true; return }
  uploadForm.role = archiveFilters.role || (roleOptions.value[0]?.value || '')
  uploadForm.name = ''
  uploadForm.mime = ''
  uploadForm.size = 0
  pendingFile = null
  uploadAction.value = ''
  showUpload.value = true
}

function onFileChange(file: any) {
  const f: File | undefined = file?.raw
  if (!f) return
  pendingFile = f
  uploadForm.name = f.name
  uploadForm.mime = f.type || ''
  uploadForm.size = f.size || 0
}

function beforeUpload(file: File) {
  if (!activityId.value) { alertText.value = '请先选择活动'; showAlert.value = true; return false }
  const base = String(FILE_UPLOAD_BASE).replace(/\/$/, '')
  const key = `uploads/activity/${activityId.value}/${uploadForm.role || 'other'}/${file.name}`
  uploadAction.value = `${base}/${key}`
  uploadHeaders.value = getUploadHeaders()
  return true
}

function sha256HexFromBytes(bytes: Uint8Array): string {
  const K = new Uint32Array([
    0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
    0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
    0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
    0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
    0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
    0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
    0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
    0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
  ])
  let h0=0x6a09e667,h1=0xbb67ae85,h2=0x3c6ef372,h3=0xa54ff53a,h4=0x510e527f,h5=0x9b05688c,h6=0x1f83d9ab,h7=0x5be0cd19
  const ml = bytes.length
  const l = ml + 1 + 8
  const padLen = ((64 - (l % 64)) % 64)
  const total = ml + 1 + padLen + 8
  const msg = new Uint8Array(total)
  msg.set(bytes,0)
  msg[ml] = 0x80
  const bitLenHi = Math.floor((ml>>>29))
  const bitLenLo = (ml<<3)>>>0
  msg[total-8] = (bitLenHi>>>24)&0xff
  msg[total-7] = (bitLenHi>>>16)&0xff
  msg[total-6] = (bitLenHi>>>8)&0xff
  msg[total-5] = (bitLenHi)&0xff
  msg[total-4] = (bitLenLo>>>24)&0xff
  msg[total-3] = (bitLenLo>>>16)&0xff
  msg[total-2] = (bitLenLo>>>8)&0xff
  msg[total-1] = (bitLenLo)&0xff
  const w = new Uint32Array(64)
  for (let i=0;i<total;i+=64){
    for (let j=0;j<16;j++){
      const k=i+j*4
      w[j] = ((msg[k]<<24)|(msg[k+1]<<16)|(msg[k+2]<<8)|msg[k+3])>>>0
    }
    for (let j=16;j<64;j++){
      const s0 = (rotr(w[j-15],7) ^ rotr(w[j-15],18) ^ (w[j-15]>>>3))>>>0
      const s1 = (rotr(w[j-2],17) ^ rotr(w[j-2],19) ^ (w[j-2]>>>10))>>>0
      w[j] = (w[j-16] + s0 + w[j-7] + s1)>>>0
    }
    let a=h0,b=h1,c=h2,d=h3,e=h4,f=h5,g=h6,h=h7
    for (let j=0;j<64;j++){
      const S1 = (rotr(e,6)^rotr(e,11)^rotr(e,25))>>>0
      const ch = ((e & f) ^ (~e & g))>>>0
      const t1 = (h + S1 + ch + K[j] + w[j])>>>0
      const S0 = (rotr(a,2)^rotr(a,13)^rotr(a,22))>>>0
      const maj = ((a & b) ^ (a & c) ^ (b & c))>>>0
      const t2 = (S0 + maj)>>>0
      h=g; g=f; f=e; e=(d + t1)>>>0; d=c; c=b; b=a; a=(t1 + t2)>>>0
    }
    h0=(h0+a)>>>0; h1=(h1+b)>>>0; h2=(h2+c)>>>0; h3=(h3+d)>>>0; h4=(h4+e)>>>0; h5=(h5+f)>>>0; h6=(h6+g)>>>0; h7=(h7+h)>>>0
  }
  return toHex(h0)+toHex(h1)+toHex(h2)+toHex(h3)+toHex(h4)+toHex(h5)+toHex(h6)+toHex(h7)
  function rotr(x: number, n: number){return (x>>>n)|(x<<(32-n))}
  function toHex(x: number){return ((x>>>24)&0xff).toString(16).padStart(2,'0')+((x>>>16)&0xff).toString(16).padStart(2,'0')+((x>>>8)&0xff).toString(16).padStart(2,'0')+((x)&0xff).toString(16).padStart(2,'0')}
}

async function sha256(file: File): Promise<string> {
  const buf = await file.arrayBuffer()
  const useSubtle = typeof crypto !== 'undefined' && !!crypto.subtle && (typeof isSecureContext === 'boolean' ? isSecureContext : (location.protocol === 'https:' || location.hostname === 'localhost'))
  if (useSubtle) {
    const digest = await crypto.subtle.digest('SHA-256', buf)
    const bytes = new Uint8Array(digest)
    return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('')
  }
  return sha256HexFromBytes(new Uint8Array(buf))
}

async function onUploadSuccess(_resp: any, file: any) {
  try {
    if (_resp && typeof _resp === 'object' && _resp.id) {
      showUpload.value = false
      reload()
      return
    }
    const f: File = file?.raw || pendingFile!
    const sum = await sha256(f)
    const base = String(FILE_ACCESS_BASE).replace(/\/$/, '')
    const key = `uploads/activity/${activityId.value}/${uploadForm.role || 'other'}/${f.name}`
    const url = `${base}/${key}`
    const payload = {
      storage_key: key,
      url,
      original_name: f.name,
      mime_type: f.type || '',
      size_bytes: f.size || 0,
      checksum_sha256: sum,
      bucket: 'uploads',
      visibility: 'public',
    }
    const { data } = await api.post('/file-assets/register', payload)
    const fileId = data?.id
    if (fileId) {
      await api.post(`/file-assets/activities/${activityId.value}/files`, { file_id: fileId, role: uploadForm.role || '' })
      if ((f.type || '').startsWith('image/') || (f.type || '').startsWith('video/')) {
        await api.post(`/file-assets/${fileId}/media`, { width: null, height: null, duration_ms: null })
      }
      showUpload.value = false
      reload()
    } else {
      alertText.value = '登记文件资产失败'
      showAlert.value = true
    }
  } catch (e: any) {
    alertText.value = '上传成功后登记失败，请检查网络或服务接口'
    showAlert.value = true
  }
}

function onUploadError(e?: any) {
  alertText.value = '上传失败，请检查文件服务或认证配置'
  showAlert.value = true
}

function submitUpload() {
  if (!activityId.value) { alertText.value = '请先选择活动'; showAlert.value = true; return }
  if (!pendingFile) { alertText.value = '请先选择文件'; showAlert.value = true; return }
  if (!uploadRef.value) return
  uploadRef.value.submit()
}

function doDavUpload(opts: any) {
  const file: File = opts.file
  const fd = new FormData()
  fd.append('activity_id', activityId.value)
  fd.append('role', uploadForm.role || '')
  fd.append('file', file)
  api.post('/file-assets/upload', fd)
    .then((resp) => {
      opts.onSuccess && opts.onSuccess(resp.data, file)
    })
    .catch((e) => {
      opts.onError && opts.onError(e)
    })
}

async function loadActivities() {
  if (activityLoading.value) return
  activityLoading.value = true
  const params: any = { page: activityPage.value, page_size: activityPageSize.value }
  if (activityFilters.name) params.name = activityFilters.name
  const { data } = await api.get('/activities', { params })
  const arr = Array.isArray(data.items) ? data.items : Array.isArray(data) ? data : []
  if (activityPage.value === 1) activityItems.value = arr
  else activityItems.value = activityItems.value.concat(arr)
  activityTotal.value = typeof data.total === 'number' ? data.total : activityItems.value.length
  activityLoading.value = false
}

function reloadActivities() {
  activityPage.value = 1
  loadActivities()
}

function onLeftScroll() {
  const el = leftListEl.value
  if (!el) return
  const nearBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 10
  if (nearBottom && activityItems.value.length < activityTotal.value && !activityLoading.value) {
    activityPage.value += 1
    loadActivities()
  }
}

function selectActivity(row: Activity) {
  activityId.value = row.id
  loadActivity()
  reload()
}

onMounted(async () => {
  await loadRoles()
  await loadActivities()
})
</script>

<style scoped>
.page { height: 100%; display: flex; flex-direction: column; box-sizing: border-box; overflow: hidden; }
.header { flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding-bottom: 8px; }
.title { font-size: 18px; font-weight: 600; }
.toolbar { flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding-bottom: 8px; }
.content { flex: 1; display: flex; flex-direction: column; min-height: 0; }
.split { flex: 1; display: flex; min-height: 0; }
.left { width: 320px; flex-shrink: 0; display: flex; flex-direction: column; border-right: 1px solid #eee; }
.left-toolbar { padding-bottom: 8px; }
.left-list { flex: 1; overflow: auto; }
.right { flex: 1; display: flex; flex-direction: column; padding-left: 12px; }
.pagination { flex-shrink: 0; display: flex; justify-content: flex-end; padding-top: 12px; background: white; border-top: 1px solid #f0f0f0; margin-top: auto; }
</style>
